package com.miku.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.miku.blog.domain.*;
import com.miku.blog.domain.dto.UserAuthDTO;
import com.miku.blog.domain.param.ReceptionParam;
import com.miku.blog.domain.pojo.LoginUser;
import com.miku.blog.domain.pojo.ResponseResult;
import com.miku.blog.domain.pojo.SystemConstance;
import com.miku.blog.domain.vo.*;
import com.miku.blog.eume.HttpStatusEnum;
import com.miku.blog.exception.SystemException;
import com.miku.blog.mapper.RoleMapper;
import com.miku.blog.service.RoleService;
import com.miku.blog.service.UserAuthService;
import com.miku.blog.mapper.UserAuthMapper;
import com.miku.blog.service.UserInfoService;
import com.miku.blog.service.UserLoginInfoService;
import com.miku.blog.utils.*;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Miku
 * @description 针对表【tb_user_auth】的数据库操作Service实现
 * @createDate 2022-05-24 23:03:39
 */
@Service
public class UserAuthServiceImpl extends ServiceImpl<UserAuthMapper, UserAuth>
        implements UserAuthService {

    @Lazy
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private RoleService roleService;
    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    private UserLoginInfoService userLoginInfoService;

    @Override
    public UserAuth getUserAuthByUserName(String userName) {

        LambdaQueryWrapper<UserAuth> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserAuth::getUsername, userName);
        return getOne(queryWrapper);
    }


    @Override
    public UserAuth getUserAuthByEmail(String email) {
        LambdaQueryWrapper<UserAuth> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserAuth::getEmail, email);
        return getOne(queryWrapper);
    }


    @Transactional
    @Override
    public ResponseResult updatePassword(PasswordDTO passwordDTO) {

        String oldPassword = passwordDTO.getOldPassword();
        String newPassword = passwordDTO.getNewPassword();
        String confirmPassword = passwordDTO.getConfirmPassword();

        if (!StringUtils.hasText(oldPassword) || !StringUtils.hasText(newPassword) || !StringUtils.hasText(confirmPassword)){
            return new ResponseResult(HttpStatusEnum.PARAM_ERROR);
        }

        LoginUser loginUser = SecurityUtils.getLoginUser();
        UserAuth userAuth = loginUser.getUserAuth();
        String password = userAuth.getPassword();


        if (newPassword.length() < SystemConstance.PASSWORD_LENGTH){
            return new ResponseResult(HttpStatusEnum.PASSWORD_LENGTH_ERROR);
        }
        if (!passwordEncoder.matches(oldPassword, password)) {
            return new ResponseResult(HttpStatusEnum.PASSWORD_ERROR);
        }
        if (!newPassword.equals(confirmPassword)) {
            return new ResponseResult(HttpStatusEnum.NEW_PASSWORD_ERROR);
        }
        if (passwordEncoder.matches(newPassword, password)) {
            return new ResponseResult(HttpStatusEnum.NEW_PASSWORD_DOES_NOT_MATCH_THE_OLD);
        }
        String encode = passwordEncoder.encode(newPassword);
        LambdaUpdateWrapper<UserAuth> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(UserAuth::getId, userAuth.getId())
                .set(UserAuth::getPassword, encode);
        if (!update(updateWrapper)) {
            return new ResponseResult(HttpStatusEnum.UPDATE_DATA_ERROR);
        }
        userAuth.setPassword(encode);
        updateRedisCacheUserAuth(null,loginUser);
        return ResponseResult.ok();
    }

    @Override
    public ResponseResult getUserList(Integer current, Integer size, @Nullable Integer loginType, @Nullable String keywords) {

        List<UserInfo> userInfoList;
        int total;
        if (Objects.isNull(loginType)) {

            Page<UserInfo> page = userInfoService.getUserInfoListByLikeNickName(current, size, keywords);
            userInfoList = page.getRecords();
            total = (int) page.getTotal();

        } else if (!StringUtils.hasText(keywords)) {

            userInfoList = new ArrayList<>();
            Page<UserLoginInfo> page = userLoginInfoService.getUserLoginInfoListByLoginType(current, size, loginType);
            List<UserLoginInfo> records = page.getRecords();
            for (UserLoginInfo loginInfo : records) {
                UserInfo userInfo = userInfoService.getUserInfoByUserId(loginInfo.getUserId());
                userInfoList.add(userInfo);
            }
            total = (int) page.getTotal();

        } else {

            userInfoList = new ArrayList<>();
            List<UserInfo> userInfos = userInfoService.getAllUserInfoListByNickName(keywords);
            for (UserInfo userInfo : userInfos) {
                long count = userLoginInfoService.getUserLoginInfoByUserIdAndLoginType(userInfo.getUserId(), loginType);
                if (count > 0){
                    userInfoList.add(userInfo);
                }
            }
            total = userInfoList.size();
        }


        List<UserAuth> userAuths = null;
        if (userInfoList.size() > 0) {
            LambdaQueryWrapper<UserAuth> queryWrapper = new LambdaQueryWrapper<>();
            userAuths
                    = userInfoList.stream()
                    .map(userInfo -> {
                        queryWrapper.clear();
                        queryWrapper.eq(UserAuth::getId, userInfo.getUserId());
                        return getOne(queryWrapper);
                    }).collect(Collectors.toList());
        }
        ArrayList<UserVo> userVoList = null;
        if (Objects.nonNull(userAuths) && userAuths.size() > 0) {
            userVoList = new ArrayList<>();
            if (userAuths.size() == userInfoList.size()) {
                for (int i = 0; i < userAuths.size(); i++) {
                    UserAuth userAuth = userAuths.get(i);
                    UserLoginInfo loginInfo = userLoginInfoService.getUserLoginInfoByUserId(userAuth.getId());

                    UserVo userVo = new UserVo();
                    BeanCopyUtils.copyProperties(userVo, userInfoList.get(i), loginInfo, userAuth);
                    userVoList.add(userVo);
                }
            } else {
                return new ResponseResult(HttpStatusEnum.DATA_EXCEPTIONS);
            }
            userVoList.stream()
                    .peek(userVo -> {
                        RoleMapper roleMapper = (RoleMapper) roleService.getBaseMapper();
                        Role role = roleMapper.getRoleByUserId(userVo.getId());
                        RoleVo roleVos = BeanCopyUtils.copyObjectByType(role, RoleVo.class);
                        userVo.setRoleList(Collections.singletonList(roleVos));
                    }).collect(Collectors.toList());

        }
        PageVo<UserVo> pageVo = new PageVo<>((long) total,userVoList);
        return ResponseResult.ok().setData(pageVo);

    }


    @Override
    public ResponseResult disableByUserId(Integer userId, Integer isDisable) {
        if ((!isDisable.equals(SystemConstance.IS_DISABLE) && !isDisable.equals(SystemConstance.NOT_DISABLE))
        ) {
            return new ResponseResult(HttpStatusEnum.PARAM_ERROR);
        }

        if (!userInfoService.disableByUserId(userId, isDisable)) {
            return new ResponseResult(HttpStatusEnum.UPDATE_DATA_ERROR);
        }

        String redisCacheKey = redisCache.getCacheObject(GenerateKeyUtils.getRedisUserCacheKey(userId));
        if (StringUtils.hasText(redisCacheKey)) {
            LoginUser loginUser = redisCache.getCacheObject(redisCacheKey);
            loginUser.getUserInfo().setIsDisable(isDisable);
            updateRedisCacheUserAuth(redisCacheKey,loginUser);
        }
        return ResponseResult.ok();
    }


    @Override
    public ResponseResult getOnlineUser(Integer current, Integer size, @Nullable String keywords) {

        //获取redis缓存中所有的登录用户的键,根据键获取所有的登录用户
        Collection<String> keys = redisCache.keys(SystemConstance.REDIS_LOGINUSER_KEY + SystemConstance.REDIS_PATTERN_KEY);
        List<OnlineUsersVo> onlineUsersVos = null;
        int count = 0;
        if (Objects.nonNull(keys) && keys.size() > 0) {
            List<UserAuth> userAuthList = keys.stream()
                    .map(key -> {
                        LoginUser loginUser = redisCache.getCacheObject(key);
                        return loginUser.getUserAuth();
                    }).collect(Collectors.toList());

            //若查询字段不为空/空字符串 , 则对用户集合进行过滤
            Stream<UserAuth> userAuthStream = userAuthList.stream();
            if (StringUtils.hasText(keywords)) {
                userAuthStream
                        = userAuthStream.filter(userAuth -> {
                    UserInfo userInfo = userInfoService.getUserInfoByUserId(userAuth.getId());
                    String nickname = userInfo.getNickname();
                    return nickname.contains(keywords);
                });
            }
            List<UserAuth> userAuths = userAuthStream.collect(Collectors.toList());
            //根据当前分页信息,获取对应的数据
            count = userAuths.size();
            if (count > (current * size)) {
                userAuths = userAuths.subList((current - 1) * size, current * size);
            } else if (count > ((current - 1) * size)) {
                userAuths = userAuths.subList((current - 1) * size, count);
            } else {
                userAuths = null;
            }

            //将分页后的数据 进行封装
            if (Objects.nonNull(userAuths)) {
                onlineUsersVos
                        = userAuths.stream()
                        .map(userAuth -> {

                            UserInfo userInfo = userInfoService.getUserInfoByUserId(userAuth.getId());
                            UserLoginInfo loginInfo = userLoginInfoService.getUserLoginInfoByUserId(userAuth.getId());
                            OnlineUsersVo onlineUsersVo = BeanCopyUtils.copyObjectByType(loginInfo, OnlineUsersVo.class);
                            onlineUsersVo.setAvatar(userInfo.getAvatar())
                                    .setNickname(userInfo.getNickname())
                                    .setUserId(userAuth.getId());
                            return onlineUsersVo;
                        }).collect(Collectors.toList());
            }
        }

        PageVo<OnlineUsersVo> pageVo = new PageVo<>((long) count, onlineUsersVos);
        return ResponseResult.ok().setData(pageVo);
    }


    @Override
    public ResponseResult offLineUser(Integer[] userIdList) {

        for (Integer userId : userIdList) {
            String redisCacheKey = GenerateKeyUtils.getRedisUserCacheKey(userId);
            String redisLoginUserKey = redisCache.getCacheObject(redisCacheKey);
            redisCache.delCacheObject(redisLoginUserKey);
            redisCache.delCacheObject(redisCacheKey);
        }
        return ResponseResult.ok();
    }


    @Override
    public ResponseResult updateEmail(UserAuthDTO userAuthDTO) {
        String email = userAuthDTO.getEmail();

        checkInfo(email,userAuthDTO.getCode(),userAuthDTO.getJwtToken());

        LambdaUpdateWrapper<UserAuth> updateWrapper = new LambdaUpdateWrapper<>();
        LoginUser loginUser = SecurityUtils.getLoginUser();
        UserAuth userAuth = loginUser.getUserAuth();
        updateWrapper.eq(UserAuth::getId,userAuth.getId())
                .set(UserAuth::getEmail,email);
        if (!update(updateWrapper)){
            return new ResponseResult(HttpStatusEnum.UPDATE_DATA_ERROR);
        }

        userAuth.setEmail(email);
        //同步更新redis
        updateRedisCacheUserAuth(null,loginUser);
        return ResponseResult.ok();
    }


    @Override
    public ResponseResult updatePassword(UserAuthDTO userAuthDTO) {
        String email = userAuthDTO.getEmail();
        String password = userAuthDTO.getPassword();

        checkInfo(email,userAuthDTO.getCode(),userAuthDTO.getJwtToken());

        if (!StringUtils.hasText(password)){
            return new ResponseResult(HttpStatusEnum.PARAM_ERROR);
        }
        if (password.length() < SystemConstance.PASSWORD_LENGTH){
            return new ResponseResult(HttpStatusEnum.PASSWORD_LENGTH_ERROR);
        }

        String encode = passwordEncoder.encode(password);

        LambdaUpdateWrapper<UserAuth> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(UserAuth::getEmail,email)
                .set(UserAuth::getPassword,encode);
        if (!update(updateWrapper)){
            return new ResponseResult(HttpStatusEnum.UPDATE_DATA_ERROR);
        }

        return ResponseResult.ok();
    }



    @Transactional
    @Override
    public ResponseResult register(UserAuthDTO userAuthDTO) {
        String email = userAuthDTO.getEmail();
        String code = userAuthDTO.getCode();
        String jwtToken = userAuthDTO.getJwtToken();
        String password = userAuthDTO.getPassword();

        checkInfo(email,code,jwtToken);


        if (!StringUtils.hasText(password)){
            return new ResponseResult(HttpStatusEnum.PARAM_ERROR);
        }
        if (password.length() < SystemConstance.PASSWORD_LENGTH){
            return new ResponseResult(HttpStatusEnum.PASSWORD_LENGTH_ERROR);
        }

        String encode = passwordEncoder.encode(password);

        String userName = new StringBuilder()
                .append(SystemConstance.USERNAME_PREFIX)
                .append(EmailUtils.generateCode())
                .append(DateUtils.getSimpleDateFormatNo().format(new Date()))
                .toString();
        UserAuth userAuth = new UserAuth();
        userAuth.setEmail(email)
                .setUsername(userName)
                .setPassword(encode);

        if (!save(userAuth)){
            return new ResponseResult(HttpStatusEnum.INSERT_DATA_ERROR);
        }

        Integer userId = userAuth.getId();

        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(userId)
                .setNickname(UUID.randomUUID().toString().replace("-",""));

        if (!userInfoService.save(userInfo)){
            throw new SystemException(HttpStatusEnum.INSERT_DATA_ERROR);
        }

        //注册后自动登录,无需在此处做多余设置
        UserLoginInfo userLoginInfo = new UserLoginInfo();
        userLoginInfo.setUserId(userId);
        if (!userLoginInfoService.save(userLoginInfo)){
            throw new SystemException(HttpStatusEnum.INSERT_DATA_ERROR);
        }

        Role role = roleService.getRoleByRoleLabel(SystemConstance.ROLE_USER);
        if (!baseMapper.addTBUserRoleByUserIdAndRoleId(userAuth.getId(),role.getId())) {
            throw new SystemException(HttpStatusEnum.INSERT_DATA_ERROR);
        }


        return ResponseResult.ok();
    }


    private void checkInfo(String email,String code,String jwtToken){
        if (!StringUtils.hasText(email) || !StringUtils.hasText(code) || !StringUtils.hasText(jwtToken)){
            throw new SystemException(HttpStatusEnum.PARAM_ERROR);
        }
        if (!EmailUtils.checkEmail(email)){
            throw  new SystemException(HttpStatusEnum.THE_MAILBOX_IS_MALFORMED);
        }
        try {
            Claims claims = JwtUtils.parseJwt(jwtToken);

            if (!claims.getId().equals(code)){
                throw new SystemException(HttpStatusEnum.EMAIL_CODE_ERROR);
            }
            if (!claims.getSubject().equals(email)) {
                throw new SystemException(HttpStatusEnum.MAILBOX_AUTHENTICATION_FAILED);
            }
        } catch (Exception exception) {
            if (exception instanceof SystemException){
                throw exception;
            }else {
                throw new SystemException(HttpStatusEnum.EMAIL_CODE_EXPIRED);
            }
        }
    }


    private void updateRedisCacheUserAuth(String redisCacheKey, LoginUser loginUser){
        if (!StringUtils.hasText(redisCacheKey)) {
            redisCacheKey = GenerateKeyUtils.generateRedisCacheKey(HttpUtils.getHttpServletRequest(), null);
        }
        redisCache.setCacheObject(redisCacheKey,
                loginUser,
                SystemConstance.REDIS_EXPIRE_TIME,
                SystemConstance.REDIS_TIMEUNIT);

        redisCache.setCacheObject(GenerateKeyUtils.getRedisUserCacheKey(loginUser.getUserAuth().getId()),
                redisCacheKey,
                SystemConstance.REDIS_ID_KEY_EXPIRE_TIME,
                SystemConstance.REDIS_TIMEUNIT);
    }
}




